﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Snowflake.Core;
using Sunny.Entity;
using Sunny.Model;
using Sunny.Model.RedisCache;
using Sunny.Model.RequestParam;
using Sunny.Model.Vo;
using Sunny.Service;
using Sunny.Util;
using Sunny.Util.Exception;

namespace Sunny.Service
{
    public class UserService
    {
        private readonly HttpContext httpContext;
        private readonly SunnyContext _sunnyContext;
        private readonly RedisService _redisService;
        private readonly IMapper _mapper;
        private readonly LoginLogService _loginLogService;
        private readonly RoleService _roleService;
        private readonly DeptService _deptService;

        public UserService(IHttpContextAccessor httpContextAccessor, SunnyContext sunnyContext,
            RedisService redisService, IMapper mapper,LoginLogService loginLogService,RoleService roleService,DeptService deptService)
        {
            httpContext = httpContextAccessor.HttpContext;
            _sunnyContext = sunnyContext;
            _redisService = redisService;
            _mapper = mapper;
            _loginLogService = loginLogService;
            _roleService = roleService;
            _deptService = deptService;
        }

        public LoginUser GetCurrentUser()
        {
            var token = httpContext.Request.Headers[Constants.Token].ToString();
            if (string.IsNullOrEmpty(token))
            {
                throw new BusinessException(CodeMessage.TokenInvalid);
            }

            return _redisService.Get<LoginUser>(UserKeyPrefix.Info, token);
            ;
        }

        public async Task<string> Login(LoginParam loginParam)
        {
            SysLoginLog sysLoginLog = new SysLoginLog();
            sysLoginLog.UserName = loginParam.Username;
            sysLoginLog.Browser = httpContext.Request.Headers["User-Agent"].ToString();
            
            if (httpContext.Connection.RemoteIpAddress != null)
            {
                sysLoginLog.Ipaddr = httpContext.Connection.RemoteIpAddress.ToString();
                sysLoginLog.LoginLocation = IpHelper.GetLocationByIp(sysLoginLog.Ipaddr);
            }
            sysLoginLog.LoginTime = DateTime.Now;

            var redisCode = _redisService.Get<string>(CommonKeyPrefix.CaptchaCode, loginParam.Uuid);
            if (redisCode == null)
            {
                sysLoginLog.Status = "0";
                sysLoginLog.Msg="验证码失效";
                await _loginLogService.Add(sysLoginLog);
                throw new BusinessException(CodeMessage.CaptchaExpire);
            }

            if (!redisCode.Equals(loginParam.Code, StringComparison.OrdinalIgnoreCase))
            {
                sysLoginLog.Status = "0";
                sysLoginLog.Msg="验证码错误";
                await _loginLogService.Add(sysLoginLog);
                throw new BusinessException(CodeMessage.CaptchaInvalid);
            }

            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserName == loginParam.Username);
            if (user == null)
            {
                sysLoginLog.Status = "0";
                sysLoginLog.Msg="用户名或密码错误";
                await _loginLogService.Add(sysLoginLog);
                throw new BusinessException(CodeMessage.UserUsernameOrPasswordError);
            }

            if (!loginParam.Password.Equals(user.Password))
            {
                sysLoginLog.Status = "0";
                sysLoginLog.Msg="用户名或密码错误";
                await _loginLogService.Add(sysLoginLog);
                throw new BusinessException(CodeMessage.UserUsernameOrPasswordError);
            }

            string token = Guid.NewGuid().ToString();
            LoginUser loginUser = new LoginUser();
            loginUser.UserId = user.UserId;
            loginUser.Avatar = user.Avatar;
            loginUser.DeptId = user.DeptId;
            loginUser.Email = user.Email;
            loginUser.NickName = user.NickName;
            loginUser.Phonenumber = user.Phonenumber;
            loginUser.Sex = user.Sex;
            loginUser.UserName = user.UserName;
            loginUser.UserType = user.UserType;
            loginUser.TokenId = token;
            loginUser.Browser = sysLoginLog.Browser;
            loginUser.Ipaddr = sysLoginLog.Ipaddr;
            loginUser.LoginLocation = sysLoginLog.LoginLocation;
            loginUser.LoginTime = DateTime.Now;
            if(loginUser.DeptId.HasValue)
            {
                var deptName = await _deptService.GetDeptAllLevelName(loginUser.DeptId.Value);
                loginUser.DeptName = deptName;
            }

            _redisService.Set(UserKeyPrefix.Info, token, loginUser);

            sysLoginLog.Status = "1";
            sysLoginLog.Msg="登录成功";
            await _loginLogService.Add(sysLoginLog);
            
            return token;
        }

        public async Task<SysUser> GetLoginUser(HttpRequest request)
        {
            var token = request.Headers[Constants.Token].ToString();
            if (string.IsNullOrEmpty(token))
            {
                throw new BusinessException(CodeMessage.TokenInvalid);
            }

            var loginUser = _redisService.Get<LoginUser>(UserKeyPrefix.Info, token);
            if (loginUser == null)
            {
                throw new BusinessException(CodeMessage.TokenInvalid);
            }

            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserId == loginUser.UserId);
            if (user == null)
            {
                throw new BusinessException(CodeMessage.UserNotExists);
            }

            return user;
        }

        public async Task<(List<UserVo> users, int total)> List(UserListParam param)
        {
            var users = _sunnyContext.SysUsers.AsQueryable();
            if (param.DeptId.HasValue)
            {
                var deptIds = await _sunnyContext.SysDepts.Where(t => t.Ancestors.Contains(param.DeptId.ToString()))
                    .Select(t => t.DeptId).ToListAsync();
                users = users.Where(t => deptIds.Contains(t.DeptId.Value) || t.DeptId == param.DeptId);
            }

            if (!string.IsNullOrEmpty(param.UserName))
            {
                users = users.Where(t => t.UserName.Contains(param.UserName));
            }

            if (!string.IsNullOrEmpty(param.Phonenumber))
            {
                users = users.Where(t => t.Phonenumber.Contains(param.Phonenumber));
            }

            if (!string.IsNullOrEmpty(param.Status))
            {
                users = users.Where(t => t.Status == param.Status);
            }

            if (param.CreateTime != null && param.CreateTime.Length == 2)
            {
                users = users.Where(t => t.CreateTime >= param.CreateTime[0] && t.CreateTime <= param.CreateTime[1]);
            }

            var total = await users.CountAsync();
            var userList = await users.OrderBy(t => t.CreateTime).Skip((param.PageNum - 1) * param.PageSize)
                .Take(param.PageSize).ToListAsync();
            var userVos = _mapper.Map<List<UserVo>>(userList);
            foreach (var userVo in userVos)
            {
                if (userVo.DeptId.HasValue)
                {
                    var dept = await _sunnyContext.SysDepts.SingleOrDefaultAsync(t => t.DeptId == userVo.DeptId.Value);
                    userVo.DeptName = dept?.DeptName;
                }
            }

            return (userVos, total);
        }

        public async Task<SysUser> GetUserById(long userId)
        {
            return await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserId == userId);
        }

        public void CheckUserAllowed(SysUser user)
        {
            if (user != null && user.IsAdmin())
            {
                throw new BusinessException(CodeMessage.AdminNotAllowedEdit);
            }
        }

        public async Task<bool> CheckPhoneExists(SysUser user)
        {
            if (string.IsNullOrEmpty(user.Phonenumber))
            {
                return false;
            }

            return await _sunnyContext.SysUsers.AnyAsync(t =>
                t.UserId != user.UserId && t.Phonenumber == user.Phonenumber);
        }

        public async Task<bool> CheckUserNameExists(SysUser user)
        {
            if (string.IsNullOrEmpty(user.UserName))
            {
                return false;
            }

            return await _sunnyContext.SysUsers.AnyAsync(t => t.UserId != user.UserId && t.UserName == user.UserName);
        }

        public async Task<bool> CheckEmailExists(SysUser user)
        {
            if (string.IsNullOrEmpty(user.Email))
            {
                return false;
            }

            return await _sunnyContext.SysUsers.AnyAsync(t => t.UserId != user.UserId && t.Email == user.Email);
        }

        public async Task Update(UserEditParam userEditParam)
        {
            var userPosts = _sunnyContext.SysUserPosts.Where(t => t.UserId == userEditParam.UserId);
            _sunnyContext.SysUserPosts.RemoveRange(userPosts);

            var userRoles = _sunnyContext.SysUserRoles.Where(t => t.UserId == userEditParam.UserId);
            _sunnyContext.SysUserRoles.RemoveRange(userRoles);

            if (userEditParam.PostIds.Any())
            {
                foreach (var postId in userEditParam.PostIds)
                {
                    await _sunnyContext.SysUserPosts.AddAsync(new SysUserPost()
                        {PostId = postId, UserId = userEditParam.UserId});
                }
            }

            if (userEditParam.RoleIds.Any())
            {
                foreach (var roleId in userEditParam.RoleIds)
                {
                    await _sunnyContext.SysUserRoles.AddAsync(new SysUserRole()
                        {RoleId = roleId, UserId = userEditParam.UserId});
                }
            }

            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserId == userEditParam.UserId);

            user.NickName = userEditParam.NickName;
            user.DeptId = userEditParam.DeptId;
            user.Phonenumber = userEditParam.Phonenumber;
            user.Email = userEditParam.Email;
            user.Sex = userEditParam.Sex;
            user.Status = userEditParam.Status;
            user.Remark = userEditParam.Remark;
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task Add(UserEditParam userEditParam)
        {
            var idWorker = new IdWorker(1, 1);
            var userId = idWorker.NextId();
            foreach (var postId in userEditParam.PostIds)
            {
                await _sunnyContext.SysUserPosts.AddAsync(new SysUserPost() {PostId = postId, UserId = userId});
            }

            foreach (var roleId in userEditParam.RoleIds)
            {
                await _sunnyContext.SysUserRoles.AddAsync(new SysUserRole() {RoleId = roleId, UserId = userId});
            }

            var user = new SysUser();
            user.UserId = userId;
            user.UserName = userEditParam.UserName;
            user.UserType = userEditParam.UserType;
            user.Password = userEditParam.Password;
            user.NickName = userEditParam.NickName;
            user.DeptId = userEditParam.DeptId;
            user.Phonenumber = userEditParam.Phonenumber;
            user.Email = userEditParam.Email;
            user.Sex = userEditParam.Sex;
            user.Status = userEditParam.Status;
            user.Remark = userEditParam.Remark;

            await _sunnyContext.SysUsers.AddAsync(user);
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task Delete(long userId)
        {
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t=>t.UserId == userId);
            user.DelFlag = 1;
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task ChangeStatus(long userId, string status)
        {
            var sysUser = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserId == userId);
            sysUser.Status = status;
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task ResetPwd(UserResetPwdParam param)
        {
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t=>t.UserId == param.UserId);
            if(user == null)
            {
                throw new BusinessException(CodeMessage.UserNotExists); 
            }
            user.Password = param.Password;
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task<AuthRoleVo> AuthRole(long userId)
        {
            AuthRoleVo authRoleVo = new AuthRoleVo();
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t=>t.UserId == userId);
            var userVo = _mapper.Map<UserVo>(user);
            userVo.Roles = await _sunnyContext.SysUserRoles.Where(t=>t.UserId == userId).Select(t=>t.RoleId).ToListAsync();

            var roles = await _roleService.GetAll();
            authRoleVo.User = userVo;
            authRoleVo.Roles = roles;
            return authRoleVo;
        }

        public async Task SetRole(UserSetRoleParam param)
        {
            var sysUserRoles = _sunnyContext.SysUserRoles.Where(t=>t.UserId == param.UserId);
            _sunnyContext.SysUserRoles.RemoveRange(sysUserRoles);

            var strRoleIds = param.RoleIds.Split(",");
            if(strRoleIds.Length > 0)
            {
                var roleIds = Array.ConvertAll(strRoleIds,long.Parse);
                foreach (var roleId in roleIds)
                {
                    await _sunnyContext.SysUserRoles.AddAsync(new SysUserRole(){UserId =  param.UserId,RoleId = roleId});
                }
            }
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task<UserVo> GetProfile()
        {
            var currentUser = GetCurrentUser();
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t=>t.UserId == currentUser.UserId);
            var userVo = _mapper.Map<UserVo>(user);
            if(user.DeptId.HasValue)
            {
                string deptName =  await _deptService.GetDeptAllLevelName(user.DeptId.Value);
                userVo.DeptName = deptName;
            }

            var roleNames = await _roleService.GetRoleNamesByUserId(user.UserId);
            userVo.RoleNames = roleNames;
            return userVo;
        }

        public async Task UpdateProfile(UserEditParam param)
        {
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t => t.UserId == param.UserId);

            user.NickName = param.NickName;
            user.Phonenumber = param.Phonenumber;
            user.Email = param.Email;
            user.Sex = param.Sex;
            await _sunnyContext.SaveChangesAsync();
        }

        public async Task UpdatePassword(UserPasswordEditParam param)
        {
            var currentUser = GetCurrentUser();
            var user = await _sunnyContext.SysUsers.SingleOrDefaultAsync(t=>t.UserId == currentUser.UserId);
            
            if(!user.Password.Equals(param.OldPassword))
            {
                throw new BusinessException(CodeMessage.UserOldPasswordInvalid);
            }
            user.Password = param.NewPassword;
            await _sunnyContext.SaveChangesAsync();
        }

        public (List<LoginUser>,int) GetOnlineUser(UserOnlineParam param)
        {
            var redisKeys = _redisService.Keys(UserKeyPrefix.Info,"*");
            
            List<LoginUser> users = new List<LoginUser>();
            foreach (var redisKey in redisKeys)
            {
                var loginUser = _redisService.Get<LoginUser>(redisKey);
                users.Add(loginUser);
            }
            if(!string.IsNullOrEmpty(param.Ipaddr))
            {
                users = users.Where(t=>t.Ipaddr.Contains(param.Ipaddr)).ToList();
            }
            if(!string.IsNullOrEmpty(param.UserName))
            {
                users = users.Where(t=>t.UserName.Contains(param.UserName)).ToList();
            }
            int total = users.Count;
            users = users.OrderByDescending(t=>t.LoginTime).Skip((param.PageNum-1)*param.PageSize).Take(param.PageSize).ToList();
            return (users,total);
        }
        
        public async Task<int> UserCount()
        {
            return await _sunnyContext.SysUsers.CountAsync();
        }
    }
}